package com.imooc.distributedemo.web3;

import com.imooc.distributedemo.DistributeDemoApplication;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.DefaultBlockParameterNumber;
import org.web3j.protocol.core.methods.response.EthBlock;
import org.web3j.protocol.core.methods.response.EthBlock.TransactionObject;
import org.web3j.protocol.core.methods.response.EthBlock.TransactionResult;
import org.web3j.protocol.http.HttpService;

import javax.annotation.PostConstruct;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@Slf4j
@Component
public class BlockScanner {


    @Value("${ethereum.rpc.url}")
    private String ethereumRpcUrl;

    @Value("${spring.redis.host}")
    private String redisHost;

    @Value("${spring.redis.port}")
    private int redisPort;

    @Value("${company.target.address}")
    private String targetAddress;


    private static final String ST_BLOCK_HEIGHT = "web3:eth:block:data:block_height"; //	6810618-18
    private static final Integer BLOCK_INTERVAL_NUM = 15;

    private Web3j web3j;
    private StringRedisTemplate stringRedisTemplate;
    private ExecutorService executorService;

    @PostConstruct
    public void init(){
        // Ensure web3j and stringRedisTemplate are initialized
        if (web3j == null || stringRedisTemplate == null) {
            throw new IllegalStateException("Web3j or StringRedisTemplate not properly initialized");
        }

        executorService = Executors.newFixedThreadPool(5);
    }

    @Autowired
    public void setWeb3j(Web3j web3j) {
        this.web3j = web3j;
    }

    @Autowired
    public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Scheduled(fixedRate = 12000) // 12秒去扫描一次
    public void scanEthBlocks(){
        try{
            // 获取当前区块高度
            BigInteger currentHeight = web3j.ethBlockNumber().send().getBlockNumber();
            // 获取存储的区块高度
            BigInteger storedHeight = getStoredBlockHeight();

            if(currentHeight.compareTo(storedHeight) > 0){
                BigInteger startHeight = storedHeight.add(BigInteger.ONE);
                BigInteger endHeight = startHeight.add(BigInteger.valueOf(BLOCK_INTERVAL_NUM)).min(currentHeight);
                for (BigInteger blockNumber = startHeight;blockNumber.compareTo(endHeight)<0;blockNumber=blockNumber.add(BigInteger.ONE)){
                    DefaultBlockParameter reqParameter = new DefaultBlockParameterNumber(blockNumber);
                    EthBlock.Block block = web3j.ethGetBlockByNumber(reqParameter, true).send().getBlock();
                    if (block != null && !CollectionUtils.isEmpty(block.getTransactions())) {
                        List<TransactionResult> thisTransactions = block.getTransactions();
                        List<Future<?>> futures = new ArrayList<>();
                        for (TransactionResult tx : thisTransactions) {
                            final BigInteger internalBlockNumber = blockNumber;
                            Future<?> future = executorService.submit(()->processTransaction(block, (EthBlock.TransactionObject) tx.get(), internalBlockNumber));
                            futures.add(future);
                        }
                        // 等待所有tasks都执行完成
                        for (Future<?> future:futures){
                            future.get();
                        }
                    }
                    // 更新Redis  ST_BLOCK_HEIGHT=web3:eth:block:data:block_height
                    stringRedisTemplate.opsForValue().set(ST_BLOCK_HEIGHT, endHeight.toString());
                }
            }

        }catch (Exception e){
            log.error("errr",e);
            System.out.println("scanEthBlocks出现错误");
        }
    }

    private void processTransaction(EthBlock.Block block,EthBlock.TransactionObject transaction, BigInteger blockNumber) {
        String[] targetAddressArr = targetAddress.split(",");
        List<String> targetAddressList = Arrays.asList(targetAddressArr);
        if(StringUtils.isEmpty(transaction.getTo())){
            return ;
        }
        String toAddress = transaction.getTo().toLowerCase();
        if(!toAddress.isEmpty() && targetAddressList.contains(toAddress.toLowerCase())){
            try {
                log.info("获取了匹配公司的交易地址",toAddress);
                System.out.println("Found target transaction in block " + blockNumber + ": " + transaction.getHash());
                handleBusiness(block, transaction);
            }catch (Exception e){
                log.error("processTransaction执行异常err block:{}",block,e);
            }
        }
    }

    private void handleBusiness(EthBlock.Block block, TransactionObject transaction) {
        BigInteger blockNumber = block.getNumber();
        System.out.println("找到包含目标地址的交易: " + transaction.getHash());
        System.out.println("区块哈希: " + block.getHash());
        System.out.println("区块编号: " + blockNumber);
        System.out.println("交易时间: " + block.getTimestamp());

        System.out.println("交易发送者: " + transaction.getFrom());
        System.out.println("交易接收者: " + transaction.getTo());
        System.out.println("交易金额: " + transaction.getValue());

        // 发送kafka 交给业务系统处理
        // 处理当前符合要求的区块  处理该区块的交易数据
    }

    private BigInteger getStoredBlockHeight() {
        String storedHeightStr = stringRedisTemplate.opsForValue().get(ST_BLOCK_HEIGHT);
        if(StringUtils.isEmpty(storedHeightStr)){
            BigInteger init = BigInteger.ZERO;
            // TODO xxx 这个区块【6810618】高度是符合目标的，按照从 6810600 开始扫描
            init = new BigInteger("6810600"); // 实际从0开始扫描
            return init;
        }
        return new BigInteger(storedHeightStr);
    }

}
